#include "log-util.h"

void *writeFile(void *);

static JavaVM *gvm;
static jobject gCallBackObj;
static jmethodID gCallBackMid;

/**
 * 当系统加载动态库的时候（在java中就是System.loadLibrary()）由JNI调用。
 * 全局只能存在一个
 * @param vm
 * @param reserved
 * @return
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    gvm = vm;
    LOGD("JNI_OnLoad Call");
    return JNI_VERSION_1_6;
}

/**
 * 总结: 分三步

    1. 根据java的obj获取jclass。jclass可以理解为java中的class对象（如果熟悉jni就没啥问题）
    2. 根据1步中的jclass和方法名字和方法的签名获取该方法的jmethodID
    3. env调用java实例的方法。（当前如果是静态的只是调用的方法不一样）
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndkjnidemo_home_threadcallback_JNIThreadCallback_nativeCallBack(JNIEnv *env,
                                                                                 jobject thiz,
                                                                                 jobject call_back) {
    // 获取java中的对象
    jclass cls = env->GetObjectClass(call_back);
    // 获取回调方法的id
    jmethodID mid = env->GetMethodID(cls, "onCall", "()V");
    // 调用java中的方法
    env->CallVoidMethod(call_back, mid);
}

/**
 *  线程的几个参数
 * pthread_create
 参数1 pthread_t* pthread 线程句柄
 参数2  pthread_attr_t const* 线程的一些属性
 参数3 void* (*__start_routine)(void*) 线程具体执行的函数
 参数4 void* 传给线程的参数
 返回值 int  0 创建成功
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndkjnidemo_home_threadcallback_JNIThreadCallback_nativeInThreadCallBack(
        JNIEnv *env, jobject thiz,
        jobject call_back) {
    // 创建一个jni中的全局引用，可以在线程里面用
    gCallBackObj = env->NewGlobalRef(call_back);
    jclass cls = env->GetObjectClass(call_back);
    gCallBackMid = env->GetMethodID(cls, "onSuccess", "(Ljava/lang/String;)V");
    // 创建一个线程
    pthread_t pthread;
    jint ret = pthread_create(&pthread, nullptr, writeFile, nullptr);
    LOGD("pthread_create ret=%d", ret);
}

/**
 * 相当于java中线程的run方法
 * jni中有写入文件的操作，记得加入Android 权限哦
 * 主要方法是JavaVM中的AttachCurrentThread和DetachCurrentThread两个方法，这两个是对应的
 * 其实还是jni中非子线程回调到java方法中的三个步骤，只不是多了AttachCurrentThread和DetachCurrentThread的操作
 *
 * @return
 */
void *writeFile(void *args) {
    // 随机字符串写入
    FILE *file;
    if ((file = fopen("/sdcard/thread_cb.txt", "a+")) == nullptr) {
        LOGE("fopen filed");
        return nullptr;
    }
    for (int i = 0; i < 10; ++i) {
        fprintf(file, "test %d\n", i);
    }
    fflush(file);
    fclose(file);
    LOGD("file write done");

    // https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html
    // 调用的话，需要JNIEnv *env
    // JNIEnv *env 无法跨越线程，只有JavaVM才能跨越线程
    // 每个原生方法的 JNIEvn 接口指针在与方法调用相关的线程中也是有效的，但是它不能被其它线程缓存或使用。
    JNIEnv *env = nullptr; // 新的env
    // 将当前native的线程附加到Java虚拟机上，返回一个属于当前线程的JNIEnv指针env
    if (gvm->AttachCurrentThread(&env, nullptr) == 0) {
        jstring jstr = env->NewStringUTF("write success");
        // 回调到java层
        env->CallVoidMethod(gCallBackObj, gCallBackMid, jstr);
        // 删除jni中全局引用
        env->DeleteGlobalRef(gCallBackObj);
        // 从Java虚拟机上分离当前native线程, 解除附加到JVM的native线程
        gvm->DetachCurrentThread();
    }
    return nullptr;
}